<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Custom Bigraph Layout</title>
  </head>

  <body>
    <div id="description">
      自定义布局二分图，根据二分图中每个部分节点与另一部分的连接关系，对节点进行排序以减少边的交错
    </div>
    <div id="mountNode"></div>
    <script src="../build/g6.js"></script>
    <script>
      const data = {
        nodes: [
          {
            id: '0',
            label: 'A',
            cluster: 'part1',
          },
          {
            id: '1',
            label: 'B',
            cluster: 'part1',
          },
          {
            id: '2',
            label: 'C',
            cluster: 'part1',
          },
          {
            id: '3',
            label: 'D',
            cluster: 'part1',
          },
          {
            id: '4',
            label: 'E',
            cluster: 'part1',
          },
          {
            id: '5',
            label: 'F',
            cluster: 'part1',
          },
          {
            id: '6',
            label: 'a',
            cluster: 'part2',
          },
          {
            id: '7',
            label: 'b',
            cluster: 'part2',
          },
          {
            id: '8',
            label: 'c',
            cluster: 'part2',
          },
          {
            id: '9',
            label: 'd',
            cluster: 'part2',
          },
        ],
        edges: [
          {
            source: '0',
            target: '6',
          },
          {
            source: '0',
            target: '7',
          },
          {
            source: '0',
            target: '9',
          },
          {
            source: '1',
            target: '6',
          },
          {
            source: '1',
            target: '9',
          },
          {
            source: '1',
            target: '7',
          },
          {
            source: '2',
            target: '8',
          },
          {
            source: '2',
            target: '9',
          },
          {
            source: '2',
            target: '6',
          },
          {
            source: '3',
            target: '8',
          },
          {
            source: '4',
            target: '6',
          },
          {
            source: '4',
            target: '7',
          },
          {
            source: '5',
            target: '9',
          },
        ],
      };

      G6.registerLayout(
        'bigraph-layout',
        {
          getDefaultCfg() {
            return {
              center: [0, 0],
              biSep: 100,
              nodeSep: 20,
              direction: 'horizontal',
              nodeSize: 20,
            };
          },
          execute() {
            const self = this;
            const center = self.center;
            const biSep = self.biSep;
            const nodeSep = self.nodeSep;
            const nodeSize = self.nodeSize;
            let part1Pos = 0,
              part2Pos = 0;
            if (self.direction === 'horizontal') {
              part1Pos = center[0] - biSep / 2;
              part2Pos = center[0] + biSep / 2;
            }
            const nodes = self.nodes;
            const edges = self.edges;
            const part1Nodes = [];
            const part2Nodes = [];
            const part1NodeMap = new Map();
            const part2NodeMap = new Map();
            // separate the nodes and init the positions
            nodes.forEach((node, i) => {
              if (node.cluster === 'part1') {
                part1Nodes.push(node);
                part1NodeMap.set(node.id, i);
              } else {
                part2Nodes.push(node);
                part2NodeMap.set(node.id, i);
              }
            });

            // order the part1 node
            part1Nodes.forEach(p1n => {
              let index = 0;
              let adjCount = 0;
              edges.forEach(edge => {
                const sourceId = edge.source;
                const targetId = edge.target;
                if (sourceId === p1n.id) {
                  index += part2NodeMap.get(targetId);
                  adjCount++;
                } else if (targetId === p1n.id) {
                  index += part2NodeMap.get(sourceId);
                  adjCount++;
                }
              });
              index /= adjCount;
              p1n.index = index;
            });
            part1Nodes.sort(function(a, b) {
              return a.index - b.index;
            });
            part2Nodes.forEach(p2n => {
              let index = 0;
              let adjCount = 0;
              edges.forEach(edge => {
                const sourceId = edge.source;
                const targetId = edge.target;
                if (sourceId === p2n.id) {
                  index += part1NodeMap.get(targetId);
                  adjCount++;
                } else if (targetId === p2n.id) {
                  index += part1NodeMap.get(sourceId);
                  adjCount++;
                }
              });
              index /= adjCount;
              p2n.index = index;
            });
            part2Nodes.sort(function(a, b) {
              return a.index - b.index;
            });

            // place the nodes
            const hLength =
              part1Nodes.length > part2Nodes.length ? part1Nodes.length : part2Nodes.length;
            const height = hLength * (nodeSep + nodeSize);
            let begin = center[1] - height / 2;
            if (self.direction === 'vertical') {
              begin = center[0] - height / 2;
            }
            part1Nodes.forEach((p1n, i) => {
              if (self.direction === 'horizontal') {
                p1n.x = part1Pos;
                p1n.y = begin + i * (nodeSep + nodeSize);
              } else {
                p1n.x = begin + i * (nodeSep + nodeSize);
                p1n.y = part1Pos;
              }
            });
            part2Nodes.forEach((p2n, i) => {
              if (self.direction === 'horizontal') {
                p2n.x = part2Pos;
                p2n.y = begin + i * (nodeSep + nodeSize);
              } else {
                p2n.x = begin + i * (nodeSep + nodeSize);
                p2n.y = part2Pos;
              }
            });
          },
        },
        'mds',
      );

      const graph = new G6.Graph({
        container: 'mountNode',
        width: 800,
        height: 600,
        layout: {
          type: 'bigraph-layout',
          biSep: 300,
          nodeSep: 50,
          nodeSize: 20,
        },
        animate: true,
        defaultNode: {
          size: [20, 20],
          color: 'steelblue',
          style: {
            lineWidth: 2,
            fill: '#fff',
          },
        },
        defaultEdge: {
          size: 1,
          color: '#e2e2e2',
        },
        nodeStateStyles: {
          selected: {
            fill: 'steelblue',
          },
        },
      });
      graph.data(data);
      graph.render();
    </script>
  </body>
</html>
